﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: LinqQuery_1.cs
//
//  Description: A storage independent query that runs on top of LINQ.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using Chili.Opf3.Storages;
using System.Linq.Expressions;
using OrderDirection = Chili.Opf3.Query.SortDirections;
using System.Reflection;
using System.Text;
using System.Data;

namespace Chili.Opf3.Linq
{
	/// <summary>
	/// This class represents a storage independent query that runs on top of Microsoft LINQ.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class represents a storage independent query. The query is then compiled by the
	/// framework and executed on a class that implements the <see cref="Opf3.Storages.IStorage">IStorage</see>
	/// interface. When creating a LinqQuery you may specify different conditions, joins and 
	/// sort options.
	/// </para>
	/// </remarks>
	/// <example>
	/// The following example shows how to use LinqQuery in your application.
	/// <code lang="CS">
	/// // Set up the command builder that converts the LINQ statements to SQL.
	/// context.Storage.StorageCommandBuilders.Add(new LinqQueryCommandBuilder());
	/// 
	/// // Create the query.
	/// var query = from u in context.GetPersistents&lt;User&gt;()
	///             where u.Age &gt; 40
	///				orderby u.Name ascending
	///             select new { Age = u.Age, u.Name };
	/// 
	/// // Now we can do different things with the query:
	/// 
	/// // Loop over the results.
	/// foreach(var item in query)
	/// {
	///     // Do something with the item.
	/// }
	/// 
	/// // Convert the query to an ObjectSet.
	/// var objectSet = query.GetObjectSet();
	/// 
	/// // Convert the query to an ObjectReader.
	/// var objectReader = query.GetObjectReader();
	/// 
	/// // ... Other code.
	/// </code>
	/// </example>	
	/// <example>
	/// The following example shows on how to use the helper classes to extend the LINQ query.
	/// <code lang="CS">
	/// // Set up the command builder that converts the LINQ statements to SQL.
	/// context.Storage.StorageCommandBuilders.Add(new LinqQueryCommandBuilder());
	/// 
	/// // Check if the name is in the list of items that we give as later arguments.
	/// var query1 = from u in context.GetPersistents&lt;User&gt;()
	///              where QueryUtility.In(u.Name, "foo", "bar")
    ///              select u;
	///             
	/// // Check if the birthday of the users is within the given range.
	/// var query2 = from u in context.GetPersistents&lt;User&gt;()
	///              where QueryUtility.Between(u.Birthday, new DateTime(1990, 1, 1), new DateTime(2000, 1, 1)
    ///              select u;
	/// 
	/// // Specify a field that is not mapped in the query.
	/// var query3 = from u in context.GetPersistents&lt;User&gt;()
	///              where QueryUtility.Field&lt;int&gt;("USERS", "AGE") > 18
	///              select u;
	///
	/// // Use the string functions to specify the SQL statement "LIKE".
	/// // We could use StartsWith, EndsWith and Contains for the different ways
	/// // of how like can be used in SQL.
	/// var query4 = from u in context.GetPersistents&lt;User&gt;()
	///              where u.Name.Contains("foo")
	///              select u;
	/// 
	/// // ... Other code.
	/// </code>
	/// </example>		
	public sealed class LinqQuery<T> : ILinqQuery, Chili.Opf3.Query.IQuery, IEnumerable<T>
	{
		private ObjectContext _context;
		private int _timeout = 30;
		private ExpressionsBag _expressions = new ExpressionsBag();

		/// <summary>
		/// Creates a new instance of the <see cref="LinqQuery{T}">LinqQuery Class</see>.
		/// </summary>
		/// <param name="context">The <see cref="ObjectContext">ObjectContext</see> instance associated
		/// with this instance.</param>
		internal LinqQuery(ObjectContext context)
		{
			if (context == null)
				throw new ArgumentNullException("context");

			_context = context;
		}

		#region ILinqQuery Members

		/// <summary>
		/// Gets the expressions of the query.
		/// </summary>
		ExpressionsBag ILinqQuery.Expressions
		{
			get { return _expressions; }
		}

		/// <summary>
		/// Gets the persistent type that's going to be the fetched by the query.
		/// </summary>
		Type ILinqQuery.PersistentType
		{
			get { return typeof(T); }
		}

		/// <summary>
		/// Gets the associated <see cref="ObjectContext">ObjectContext</see>.
		/// </summary>
		ObjectContext ILinqQuery.Context
		{
			get { return _context; }
		}

		#endregion

		/// <summary>
		/// Gets the expressions of the query.
		/// </summary>
		internal ExpressionsBag Expressions
		{
			get { return _expressions; }
		}

		/// <summary>
		/// Creates a new object that is a copy of the current instance.
		/// </summary>
		internal LinqQuery<TResult> Clone<TResult>()
		{
			LinqQuery<TResult> result = new LinqQuery<TResult>(_context);
			result._timeout = _timeout;
			result._expressions = (ExpressionsBag)_expressions.Clone();

			return result;
		}

		#region IQuery Members

		/// <summary>
		/// Converts the <see cref="Chili.Opf3.Query.IQuery">IQuery</see> to an
		/// <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see> that is then executed on a
		/// specific <see cref="Opf3.Storages.IStorage">storage</see>.
		/// </summary>
		/// <returns>
		/// A new <see cref="Chili.Opf3.Query.CompiledQuery">CompiledQuery</see> that has been generated using this instance.
		/// </returns>
		/// <param name="storage">
		/// Class that implements the <see cref="Opf3.Storages.IStorage">IStorage</see>
		/// interface. The query is executed on that storage.
		/// </param>
		/// <exception cref="ArgumentNullException">Storage is null.</exception>
		public Chili.Opf3.Query.CompiledQuery ToCompiledQuery(IStorage storage)
		{
			if (storage == null)
				throw new ArgumentNullException("storage");

			// Use the IStorageCommandBuilder to compile this query.
			IStorageCommandBuilder scb = storage.StorageCommandBuilders.GetStorageCommandBuilder(this);
			return scb.ToCompiledQuery(storage, this);
		}

		/// <summary>
		/// Specifies the wait time before terminating the attempt to execute a command and generating an error.
		/// </summary>
		/// <value>An integer that specifies the wait time in seconds.</value>
		/// <exception cref="ArgumentException">Value is lower then zero.</exception>
		public int Timeout
		{
			get { return _timeout; }
			set
			{
				if (value < 0)
					throw new ArgumentException("Value is lower then zero.");

				_timeout = value;
			}
		}

		/// <summary>
		/// Returns the <see cref="Opf3.Query.QueryTypes">QueryType</see> of the LinqQuery.
		/// </summary>
		/// <value>
		/// A member of the <see cref="Opf3.Query.QueryTypes">QueryType</see>
		/// enumeration.
		/// </value>
		public Chili.Opf3.Query.QueryTypes QueryType
		{
			get { return Chili.Opf3.Query.QueryTypes.Load; }
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Gets an enumerator that allows enumerating through all the results of the query.
		/// </summary>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return ((IEnumerable<T>)this).GetEnumerator();
		}

		#endregion

		#region IEnumerable<T> Members

		/// <summary>
		/// Gets an enumerator that allows enumerating through all the results of the query.
		/// </summary>
		IEnumerator<T> IEnumerable<T>.GetEnumerator()
		{
			// Get the result for the query.
			ObjectSet<T> result = _context.GetObjectSet<T>(this);
			// Loop over the results and return them one by one.
			foreach (T item in result)
			{
				yield return item;
			}
		}

		#endregion

		/// <summary>
		/// Gets an <see cref="ObjectReader{T}">ObjectReader</see> that holds the results
		/// returned by this query.
		/// </summary>
		public ObjectReader<T> GetObjectReader()
		{
			return _context.GetObjectReader<T>(this);
		}

		/// <summary>
		/// Gets an <see cref="ObjectSet{T}">ObjectSet</see> that holds the results 
		/// returned by this query.
		/// </summary>
		/// <returns></returns>
		public ObjectSet<T> GetObjectSet()
		{
			return _context.GetObjectSet<T>(this);
		}

		/// <summary>
		/// Gets the first object that is returned by the query.
		/// </summary>
		public T GetObject()
		{
			return _context.GetObject<T>(this);
		}

		/// <summary>
		/// Returns a string representation for the query.
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			// Create the query and return the string representation.
			IDbCommand cmd = this.ToCompiledQuery(_context.Storage).Command;
			return /*base.ToString() + "; " + */ cmd.CommandText;
		}
	}
}
